using System.Runtime.CompilerServices;

namespace IFoxCAD.WPF;

public class EnumSelection<T> : ObservableObject
    where T : struct, IComparable, IFormattable, IConvertible
{
    /// <summary>
    /// Enum uses flags?
    /// </summary>
    private readonly bool _isFlagged;

    /// <summary>
    /// Can be deselected? (Radio buttons cannot deselect, checkboxes can)
    /// </summary>
    private readonly bool _canDeselect;

    /// <summary>
    /// what is considered the "blank" value if it can be deselected?
    /// </summary>
    private readonly T _blankValue;

    public EnumSelection(T value, bool canDeselect) : this(value, canDeselect, default)
    {
    }

    public EnumSelection(T value, T blankValue) : this(value, true, blankValue)
    {
    }

    public EnumSelection(T value, bool canDeselect = false, T blankValue = default)
    {
        if (!typeof(T).IsEnum)
            throw
                new ArgumentException(
                    $"{nameof(T)} must be an enum type"); // I really wish there was a way to constrain generic types to enums...
        _isFlagged = typeof(T).IsDefined(typeof(FlagsAttribute), false);
        Value = value;
        _canDeselect = canDeselect;
        _blankValue = blankValue;
    }

    /// <summary>
    /// stored value of the Enum
    /// </summary>
    public T Value
    {
        get;
        set
        {
            if (field.Equals(value))
                return;
            field = value;
            OnPropertyChanged();
            // Notify that the indexer property has changed
            OnPropertyChanged("Item[]");
        }
    }

    [IndexerName("Item")]
    public bool this[T key]
    {
        get
        {
            var iKey = (int)(object)key;
            return _isFlagged ? ((int)(object)Value & iKey) == iKey : Value.Equals(key);
        }
        set
        {
            if (_isFlagged)
            {
                var iValue = (int)(object)Value;
                var iKey = (int)(object)key;
                if ((iValue & iKey) == iKey == value)
                    return;
                if (value)
                    Value = (T)(object)(iValue | iKey);
                else
                    Value = (T)(object)(iValue & ~iKey);
            }
            else
            {
                if (Value.Equals(key) == value)
                    return;
                if (!value && !_canDeselect)
                    return;
                Value = value ? key : _blankValue;
            }
        }
    }
}